﻿using System;
using System.Net;
using System.Net.Sockets;
using Netbits.Common.Conditions;
using Netbits.Common.Threading;

namespace Netbits.Common.Network
{
	/// <summary>
	/// Represents a TCP listener.
	/// </summary>
	public sealed class TcpServerListener : IDisposable
	{
		#region Private Variables

		TcpListener _Listener;
		SaveEventHandler<TcpClient> _EventAcceptClient;
		readonly int _BackLog;
		
		int _IsListenerRunning;
		object _LockListener;
		bool _Disposed;

		#endregion

		#region Constructors

		/// <summary>
		/// Create a new instance of a TCP listener with specified IPEndPoint
		/// Default backlog count is 20
		/// </summary>
		/// <param name="BindTo_">Bind to the specified local endpoint</param>
		public TcpServerListener( IPEndPoint BindTo_ ) : this( BindTo_, 20 )
		{
		}

		/// <summary>
		/// Create a new instance of a TCP listener with specified IPEndPoint and the number of backlogs 
		/// </summary>
		/// <param name="BindTo_">Bind to the specified local endpoint</param>
		/// <param name="BackLog_">The number of backlog</param>
		public TcpServerListener( IPEndPoint BindTo_, int BackLog_ )
		{
			Condition.Requires( BindTo_, "BindTo_" ).IsNotNull();

			_BackLog = BackLog_;
			_LockListener = new object();
			_Listener = new TcpListener( BindTo_ );
			_EventAcceptClient = new SaveEventHandler<TcpClient>( 1 );
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Returns the IPEndPoint binded to
		/// </summary>
		public IPEndPoint BindTo
		{
			get
			{
				lock( _LockListener )
				{
					if( _Listener == null )
						return null;

					if( _Listener.LocalEndpoint == null )
						return null;

					return (IPEndPoint)_Listener.LocalEndpoint;
				}
			}
		}

		/// <summary>
		/// Returns if this listener is listening for clients
		/// </summary>
		public bool IsRunning
		{
			get
			{
				return _IsListenerRunning > 0;
			}
		}

		/// <summary>
		/// Returns the event-handler if a new client was accepted.
		/// </summary>
		public SaveEventHandler<TcpClient> AcceptClients
		{
			get
			{
				return _EventAcceptClient;
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Start the listener and accept new client connections
		/// </summary>
		public void Start()
		{
			lock( _LockListener )
			{
				if( _IsListenerRunning > 0 )
					return;

				_Listener.Start( _BackLog );
				_Listener.Server.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true );
				_Listener.Server.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.DontLinger, true );

				_StartAcceptClient();

				System.Threading.Interlocked.Exchange( ref _IsListenerRunning, 1 );
			}
		}

		/// <summary>
		/// Stop the listener.
		/// </summary>
		public void Stop()
		{
			lock( _LockListener )
			{
				if( _IsListenerRunning == 0 )
					return;

				_Listener.Stop();
				System.Threading.Interlocked.Exchange( ref _IsListenerRunning, 0 );
			}
		}

		#endregion

		#region Private Functions

		void _StartAcceptClient()
		{
			try
			{
				lock( _LockListener )
				{
					_Listener.BeginAcceptTcpClient( _AcceptCallback, this );
				}
			}
			catch
			{
				Stop();
			}
		}

		void _AcceptCallback( IAsyncResult Result_ )
		{
			try
			{
				lock( _LockListener )
				{
					if( _Listener == null )
						return;

					TcpClient client = _Listener.EndAcceptTcpClient( Result_ );
					_EventAcceptClient.Notify( client );

					_StartAcceptClient();
				}
			}
			catch
			{
				Stop();
			}
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Dispose all resources and stop the listener
		/// </summary>
		public void Dispose()
		{
			if( !_Disposed )
			{
				Stop();

				_Listener = null;
				_LockListener = null;

				if( _EventAcceptClient != null )
				{
					_EventAcceptClient.Dispose();
					_EventAcceptClient = null;
				}

				_Disposed = true;
			}

			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
